/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 周斌
 * Date: 2024-03-29
 * Time: 17:27
 */
public class Sort {
    /**
     * 直接插入排序
     * 时间复杂度 0(n^2)
     * 空间复杂度 O(1)
     * 稳定性：稳定的
     * @param array
     */
    public static void insertSort(int[] array) {
        //从数组的第二个数开始比较
        for (int i = 1; i < array.length; i++) {
            //将i下标的元素放入临时变量tmp中
            int tmp = array[i];
            int j = i-1;
            //j是从i-1开始
            for (; j >=0 ; j--) {
                //比较j下标的值与tmp的值
                if(array[j] > tmp) {
                    //array[j] > tmp
                    array[j+1] = array[j];
                } else {
                    //array[j] < tmp
                    break;
                }
            }
            //走到这里说明要么就是array[j] < tmp 要么就是j不满足条件
            array[j+1] = tmp;
        }
    }

    /**
     * 希尔排序
     * 时间复杂对 O(n^2)
     * 空间复杂度 O(1)
     * 稳定性：不稳定
     * @param array
     */

    public static void shellSort(int[] array) {
        //确定分组
        int gap = array.length;
        while(gap > 1) {
            gap /= 2;
            insertSort1(array,gap);
        }
    }
    private static void insertSort1(int[] array,int gap) {
        //从数组的第gap个数开始比较
        for (int i = gap; i < array.length; i++) {
            //将i下标的元素放入临时变量tmp中
            int tmp = array[i];
            int j = i-gap;
            //j是从i-gap开始
            for (; j >=0 ; j -= gap) {
                //比较j下标的值与tmp的值
                if(array[j] > tmp) {
                    //array[j] > tmp
                    array[j+gap] = array[j];
                } else {
                    //array[j] < tmp
                    break;
                }
            }
            //走到这里说明要么就是array[j] < tmp 要么就是j不满足条件
            array[j+gap] = tmp;
        }
    }

    /**
     * 选择排序
     * 时间复杂对 O(n^2)
     * 空间复杂度 O(1)
     * 稳定性：不稳定
     * @param array
     */

    public static void selectSort1(int[] array) {
        for (int i = 0; i < array.length; i++) {
            //先将i的下标存储在minIndex中;
            int minIndex = i;
            for (int j = i+1; j <array.length; j++) {
                if(array[j] <array[minIndex]) {
                    //通过遍历j来判断是否小于minIndex下标的值，小于则更新minIndex的值，minIndex存储的是下标，不是下标的值;
                    minIndex = j;
                }
            }
            //找到了最小的值再交换
            swap(array,minIndex,i);
        }
    }
    private static void swap(int[] array,int x,int y) {
        int tmp = array[x];
        array[x] = array[y];
        array[y] = tmp;
    }


//    public static void selectSort2(int[] array) {
//        int left = 0;
//        int right = array.length-1;
//        //先在minIndex和maxIndex中赋值为left下标
//        int minIndex = left;
//        int maxIndex = left;
//        while(left < right) {
//            for (int i = left+1; i <= right ; i++) {
//                //如果i遍历的值小于minIndex值则更新minIndex
//                if(array[i] <array[minIndex]) {
//                    minIndex = i;
//                }
//                //如果i遍历的值大于maxIndex值则更新maxIndex
//                if(array[i] > array[maxIndex]) {
//                    maxIndex = i;
//                }
//            }
//            //第一次交换 最大值可能是left,然后被换到了minIndex的位置
//            swap(array,minIndex,left);
//            if(maxIndex == left) {
//                minIndex = maxIndex;
//            }
//            swap(array,maxIndex,right);
//            left++;
//            right--;
//        }
//    }


    /**
     * 堆排序:
     * 时间复杂度：O(N*logN)
     * 空间复杂度：O(1)
     * 稳定性：不稳定的排序
     * @param array
     */
    public static void heapSort(int[] array){
        //创建大根堆
        createHeap(array);
        int end = array.length-1;
        while (end > 0) {
            swap(array,0,end);
            siftDown(array,0,end);
            end--;
        }
    }

    private static void createHeap(int[] array) {
        for (int parent = (array.length-1-1)/2; parent >= 0 ; parent--) {
            siftDown(array,parent,array.length);
        }
    }

    private static void siftDown(int[] array,int parent,int len) {
        int child = (2*parent)+1;
        while (child < len) {
            if(child + 1 < len && array[child] < array[child+1]) {
                child = child+1;
            }
            if(array[child] > array[parent]) {
                swap(array,child,parent);
                parent = child;
                child = 2*parent+1;
            }else {
                break;
            }
        }
    }

    /**
     * 冒泡排序
     * 不带优化的情况下：
     * 时间复杂度：
     *     最好和最坏 都是O(N^2)
     * 空间复杂度：O(1)
     * 稳定性：稳定的
     * @param array
     */
    public static void bubbleSort(int[] array) {
        //i代表趟数 10 -》 9趟
        for (int i = 0; i < array.length-1; i++) {
            boolean flg = false;
            for(int j = 0;j < array.length-1-i;j++) {
                if(array[j] > array[j+1]) {
                    swap(array,j,j+1);
                    flg = true;
                }
            }
            //没有交换证明有序了
            if(flg == false) {
                return;
            }
        }
    }

    /**
     * 快速排序
     * 时间复杂度：最好情况下：O(N*logN)
     *          最坏情况下：O(N^2)  有序 / 逆序（变成冒泡排序）
     * 空间复杂度：最好情况下：O(logN)
     *           最坏情况下：O(N)
     * 稳定性：不稳定的
     * @param array
     */
    public static void quickSort(int[] array) {
        quick(array,0,array.length-1);
    }

    private static void quick (int[] array ,int start ,int end) {
        //1.递归条件
        if(start >= end) {
            return;
        }
        //2.找基准

        //优化1： 面对数组有序的情况下，该优化有很大的意义，加快了运行时间
        //index是取数组array 0下标  中间下标   最后一个下标中值第二大的下标
        int index = midNum(array,start,end);
        //将该下标换到数组0下标，并以该下标为基准。
        swap(array,start,index);

        //优化2：在数组趋于有序的情况下，采取直接插入排序，可以加快运行时间
        if(end - start +1 <=10) {
            insertSort3(array,start,end);
            return;
        }

        //int midIndex = partitionHoare(array,start,end);
        int midIndex1 = partition(array,start,end);
        //3.递归左右
        /*quick(array,start,midIndex-1);
        quick(array,midIndex+1,end);*/
        quick(array,start,midIndex1-1);
        quick(array,midIndex1+1,end);
    }


    public static void insertSort3(int[] array ,int start,int end) {
        for (int i = start+1; i <= end; i++) {
            int tmp = array[i];
            int j = i-1;
            //j是从i-1开始
            for (; j >=0 ; j--) {
                //比较j下标的值与tmp的值
                if(array[j] > tmp) {
                    //array[j] > tmp
                    array[j+1] = array[j];
                } else {
                    //array[j] < tmp
                    break;
                }
            }
            //走到这里说明要么就是array[j] < tmp 要么就是j不满足条件
            array[j+1] = tmp;
        }
    }


    //Hoare法
   /* private static int partitionHoare(int[] array,int left,int right) {
        //定基准
        int tmp = array[left];
        //记录基准的下标s
        int s = left;
        while(left < right) {
            //以左边为基准时，需要right先移动，left后移动。
            // 原因：如果你left先移动，就会出现与基准的大的数交换，那么就不能保证基准的左边都比基准小，右边比基准大。

            //array[right] >= tmp 加等号的目的是基准是right下标的值，如果没有等于号，那么会将基准更换
            while(left < right && array[right] >= tmp) {
                right--;
            }
         //array[left] <= tmp 加等号的目的是基准是left下标的值，如果没有等于号，那么会将基准更换
            while(left < right && array[left] <= tmp) {
                left++;
            }
            //满足left下标大于tmp并且right下标小于tmp，交换两下标的值
            swap(array,left,right);
        }
        //说明left与right相遇了，将该下标的值与tmp交换
        swap(array,s,left);

        return left;
    }*/

    //挖坑法
    private static int partition(int[] array,int left,int right) {
        //将基准存储在tmp中
        int tmp = array[left];
        while(left < right) {
            //以左边为基准时，需要right先移动，left后移动。
            // 原因：如果你left先移动，就会出现与基准的大的数交换，那么就不能保证基准的左边都比基准小，右边比基准大。

            //array[right] >= tmp 加等号的目的是基准是right下标的值，如果没有等于号，那么会将基准更换
            while(left < right && array[right] >= tmp) {
                right--;
            }
            //说明array[right] < tmp 则交换
            array[left] = array[right];
            //array[left] <= tmp 加等号的目的是基准是left下标的值，如果没有等于号，那么会将基准更换
            while(left < right && array[left] <= tmp) {
                left++;
            }
            //说明array[left] > tmp 则交换
            array[right] = array[left];
        }
        //说明left与right相遇了，将tmp值放入left下标即可。
        array[left] = tmp;

        return left;
    }
    //三数取中法
    private static int midNum(int[] array,int left,int right) {
        int mid = left + ((right-left) >> 1);
        if(array[left] < array[right]) {
            if(array[mid] < array[left]) {
                return left;
            }
            if(array[mid] > array[right]) {
                return right;
            }
        }
        if(array[left] > array[right]) {
            if(array[mid] >array[left]) {
                return left;
            }
            if(array[mid] <array[right]) {
                return right;
            }
        }
        return mid;
    }

    /**
     * 归并排序
     * 时间复杂度：O(n*logN)
     * 空间复杂度：O(N)
     * 稳定性：稳定的
     */
    public static void mergeSort(int[] array) {
        mergeFunc(array,0,array.length-1);
    }

    private static void mergeFunc(int[] array,int left,int right) {
        if(left >= right) {
            return;
        }
        int mid = left + ((right-left) >> 1);
        mergeFunc(array,left,mid);
        mergeFunc(array,mid+1,right);
        //左边分解完，右边分解完，开始合并
        merge(array,left,mid,right);
    }

    private static void merge(int[] array,int left,int mid,int right) {
        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;
        int[] tmpArr = new int[right-left+1];
        int k = 0;
        //1.保证两个表 都有数据
        while (s1 <= e1 && s2 <= e2) {
            if(array[s1] <= array[s2]) {
                tmpArr[k++] = array[s1++];
            }else {
                tmpArr[k++] = array[s2++];
            }
        }
        //2. 看哪个数组 还有数据 拷贝回去
        while (s1 <= e1) {
            tmpArr[k++] = array[s1++];
        }
        while (s2 <= e2) {
            tmpArr[k++] = array[s2++];
        }
        //3.拷贝到源数组
        for (int i = 0; i < k; i++) {
            array[i+left] = tmpArr[i];
        }
    }

    /**
     * 计数排序
     *
     */
    public static void countSort(int[] array) {
        //1.求最值
        int min = array[0];
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            //求最小值
            if(array[i] < min) {
                min = array[i];
            }
            //求最大值
            if(array[i] > max) {
                max = array[i];
            }
        }
        //2.创建计数数组，并初始化
        int[] count = new int[max-min+1];
        for (int i = 0; i < array.length; i++) {
            int index = array[i] - min;
            count[index]++;
        }

        //3.遍历计数数组,并将数据放入原数组array中
        int k = 0; //k为array下标
        for (int i = 0; i < count.length; i++) {
            while(count[i] != 0) {
                array[k] = i+min;
                k++;
                count[i]--;
            }
        }

    }

}
